#define DEBUG 1		// 1 during development 0 at other times

#include <stdio.h>
#include <sndfile.h>
#include <portaudio.h>
#include <string.h>  // For "strdup()"
#include <libintl.h>  // For "gettext()"
#include <math.h>

#ifdef WIN32
#elif __BEOS__
#elif __APPLE__
#else
# include "sys/stat.h" // for mkdir()
# include "sys/time.h" // for gettimeofday()
#endif

#include "tp_magic_api.h"  // Tux Paint "Magic" tool API header
#include "SDL_image.h"  // For IMG_Load(), to load our PNG icon

#ifndef NOSOUND
# include "SDL_mixer.h"  // For Mix_LoadWAV(), to load our sound effects
#endif

#define TRUE 1
#define FALSE 0

enum
{
	TOOL_TEMPLATE,
	NUM_TOOLS
};

typedef struct {
	const char *icon_filename;
	const char *name;
	const char *desc;
} toolsstru;

toolsstru tools[NUM_TOOLS] = 
{
	{
		"slippery.png",
		gettext_noop ( "Memo Rainbow"),
		gettext_noop ( "Memo Rainbow"),
	}
};

#define FRAMES_PER_BUFFER 256
#define	INPUT_CHANNELS 2
#define PA_SAMPLE_TYPE  paFloat32

#define NUM_MEMORAINBOW_COLORS 7
#define MEMORAINBOW_RADIUS 64

#define SAMPLE_RATE 48000
#define RINGMODULATE_HZ 55
#define SINE_PERIOD (SAMPLE_RATE/RINGMODULATE_HZ)

typedef float SAMPLE;

typedef struct
{
	unsigned int phase;
} recordData;

static const double sampling_rates[] = {
	48000.0, 44100.0, 22050.0, 8000.0
};

static const int memorainbow_async_hexes[NUM_MEMORAINBOW_COLORS][3] = {
// approximate colours arranged by frequency in the visual spectrum (a.k.a ROY G BIV):
  {0xee, 0x82, 0xee},	// Violet
  {0x4b, 0x00, 0x82},	// Indigo
  {0x00, 0x00, 0xff},	// Blue
  {0x00, 0x80, 0x00},	// Green
  {0xff, 0xff, 0x00},	// Yellow
  {0xff, 0xa5, 0x00},	// Orange
  {0xff, 0x00, 0x00},	// Red
};

static float sinftable[ FRAMES_PER_BUFFER];
static float cosftable[ FRAMES_PER_BUFFER];
static float sine[ SINE_PERIOD];

static PaTime max_buffer_time =  0.0;


static void sincosftable_init() {
	int i;
	for( i=0; i < FRAMES_PER_BUFFER; i++) {
		sinftable[i] = sinf( ((float)i/FRAMES_PER_BUFFER) * M_PI * 2. );
		cosftable[i] = cosf( ((float)i/FRAMES_PER_BUFFER) * M_PI * 2. );
	}
	fprintf( stderr, "SINE_PERIOD == %f samples at %d sample rate.\n", (float)SINE_PERIOD, SAMPLE_RATE);
	for( i=0; i < SINE_PERIOD; i++) {
		sine[i] = sinf( ((float)i/((float)SINE_PERIOD)) * M_PI * 2. );
	}
}

static double probed_sample_rate = 0.0;

static double best_sample_rate() {
	if( probed_sample_rate == 0.0) {
		PaStreamParameters inputParameters;
		int i;
		
		inputParameters.device = Pa_GetDefaultInputDevice(); /* default input device */
		inputParameters.channelCount = INPUT_CHANNELS;
		inputParameters.sampleFormat = PA_SAMPLE_TYPE;
		inputParameters.suggestedLatency = Pa_GetDeviceInfo( inputParameters.device )->defaultLowInputLatency;
		inputParameters.hostApiSpecificStreamInfo = NULL;
	
		for( i=0; i < sizeof( sampling_rates); i++) {
			if( Pa_IsFormatSupported( &inputParameters, NULL, sampling_rates[i]) == 0) {
				probed_sample_rate = sampling_rates[i];
				goto done;
			}
		}
		probed_sample_rate = 0.0;
	}
done:
	return probed_sample_rate;	// no supported sampling rates found!  what to do?   sample rate == 0
}

#define RING_BUFFER_SIZE 16
static SAMPLE ring_buffer[ RING_BUFFER_SIZE][ FRAMES_PER_BUFFER][INPUT_CHANNELS];
static PaTime ring_buffer_time[ RING_BUFFER_SIZE];
static int ring_head = 0;
static int ring_tail = 0;

static int inputCallback(const void *inputBuffer, void *outputBuffer,
                           unsigned long framesPerBuffer,
                           const PaStreamCallbackTimeInfo* timeInfo,
                           PaStreamCallbackFlags statusFlags,
                           void *userData ){
	
	recordData *userDatap = (recordData*)userData;

	// copy snapshot of audio into ring_buffer...
	memcpy( &ring_buffer[ring_head][0], inputBuffer, framesPerBuffer*INPUT_CHANNELS*sizeof( SAMPLE));
	ring_buffer_time[ring_head] = timeInfo->inputBufferAdcTime;
	ring_head = ++ring_head % RING_BUFFER_SIZE;

	return paContinue; 
}

static int outputCallback(const void *inputBuffer, void *outputBuffer,
                           unsigned long framesPerBuffer,
                           const PaStreamCallbackTimeInfo* timeInfo,
                           PaStreamCallbackFlags statusFlags,
                           void *userData ){
	
	recordData *userDatap = (recordData*)userData;
	userDatap->phase = userDatap->phase % SINE_PERIOD;

	int rt;
	if( (rt=ring_tail) != ring_head) {
		float *out = (float*)outputBuffer;
		int i;
		
		for( i=0; i<framesPerBuffer; i++ )
		{
			*out++ = ring_buffer[rt][i][0] * sine[userDatap->phase];  /* left */
			*out++ = ring_buffer[rt][i][1] * sine[userDatap->phase];  /* right */
			userDatap->phase = ++userDatap->phase % SINE_PERIOD;
		}
		ring_tail = ++ring_tail % RING_BUFFER_SIZE;
	} else {
		int i;
		float *out = (float*)outputBuffer;
		for( i=0; i<framesPerBuffer; i++ )
		{
			*out++ = 0.0;  /* left */
			*out++ = 0.0;  /* right */
		}
	}

	return paContinue; 
}

static	PaStream *pa_input_stream, *pa_output_stream;

static recordData userdata = { 0};

int init_portaudio(){
	PaStreamParameters inputParameters, outputParameters;
    	PaError err;
	PaHostErrorInfo*  herr;
	int i;
	double desired_sample_rate;

    	err = Pa_Initialize();
	if( err != paNoError ) goto done;
	
	inputParameters.device = Pa_GetDefaultInputDevice(); /* default input device */
	desired_sample_rate = best_sample_rate();

	if (inputParameters.device == paNoDevice) {
		fprintf(stderr,"Error: No default input device.\n");
		goto done;
	}
    	inputParameters.channelCount = INPUT_CHANNELS;
    	inputParameters.sampleFormat = PA_SAMPLE_TYPE;
    	inputParameters.suggestedLatency = Pa_GetDeviceInfo( inputParameters.device )->defaultLowInputLatency;
    	inputParameters.hostApiSpecificStreamInfo = NULL;

	outputParameters.device = Pa_GetDefaultOutputDevice();  /* default output device */
	fprintf( stderr, "output Device == %d\n", outputParameters.device);

	if (outputParameters.device == paNoDevice) {
		fprintf(stderr,"Error: No default output device.\n");
		goto done;
	}
	outputParameters.channelCount = 2;
	outputParameters.sampleFormat = PA_SAMPLE_TYPE;
	outputParameters.suggestedLatency = Pa_GetDeviceInfo( outputParameters.device )->defaultLowOutputLatency;
	outputParameters.hostApiSpecificStreamInfo = NULL;

    	err = Pa_OpenStream(
              &pa_output_stream,
              NULL,
              &outputParameters,
              desired_sample_rate,
              FRAMES_PER_BUFFER,
              paClipOff,
              outputCallback,
              &userdata );
	if( err != paNoError ) goto done;

    	err = Pa_OpenStream(
              &pa_input_stream,
              &inputParameters,
              NULL,
              desired_sample_rate,
              FRAMES_PER_BUFFER,
              paClipOff,
              inputCallback,
              NULL );
	if( err != paNoError ) goto done;

    	err = Pa_StartStream( pa_output_stream );
	if( err != paNoError ) goto done;

    	err = Pa_StartStream( pa_input_stream );
	if( err != paNoError ) goto done;

done:
	if( err != paNoError ) {
		fprintf( stderr, "An error occured while using portaudio\n" );
		if( err == paUnanticipatedHostError )
		{
			fprintf( stderr, " unanticipated host error.\n");
			herr = Pa_GetLastHostErrorInfo();
			if (herr)
			{
			fprintf( stderr, " Error number: %ld\n", herr->errorCode );
			if (herr->errorText)
				fprintf( stderr, " Error text: %s\n", herr->errorText );
			}
			else
			fprintf( stderr, " Pa_GetLastHostErrorInfo() failed!\n" );
		}
		else
		{
			fprintf( stderr, " Error number: %d\n", err );
			fprintf( stderr, " Error text: %s\n", Pa_GetErrorText( err ) );
		}
		
		err = 1;          /* Always return 0 or 1, but no other return codes. */
	}
	
	return err;
}

/* --------------------- */
Uint32 memorainbow_async_api_version ( void )
{
#ifdef DEBUG
	fprintf( stderr, "memorainbow_async_api_version\n");
#endif
	return TP_MAGIC_API_VERSION;
}

/* --------------------- */
int memorainbow_async_init ( magic_api * api )
{
	char fname[ 4096];
#ifdef DEBUG
	fprintf( stderr, "memorainbow_async_init\n");
#endif
	sincosftable_init();
	return 1;
}


/* --------------------- */
int memorainbow_async_get_tool_count ( magic_api * api )
{
#ifdef DEBUG
	fprintf( stderr, "memorainbow_async_get_tool_count\n");
#endif
	return NUM_TOOLS;
}


/* --------------------- */
SDL_Surface * memorainbow_async_get_icon ( magic_api * api, int which )
{
	SDL_Surface *image;
	char fname[4096];

#ifdef DEBUG
	fprintf( stderr, "memorainbow_async_get_icon\n");
#endif

	snprintf ( fname, sizeof ( fname ), "%s/images/magic/%s",
	           api->data_directory, tools[which].icon_filename );
#ifdef DEBUG
	fprintf( stderr, "DEBUG: Loading image %s\n", fname);
#endif

	image = IMG_Load ( fname );
	if( !image) {
		fprintf( stderr, "IMG_Load( %s): %s\n", fname, IMG_GetError());
	}
	return image;
}


/* --------------------- */
char * memorainbow_async_get_name ( magic_api * api, int which )
{
	const char * our_name_english;
	const char * our_name_localized;

#ifdef DEBUG
	fprintf( stderr, "memorainbow_async_get_name\n");
#endif
	our_name_english = tools[which].name;
	our_name_localized = gettext ( our_name_english );
	return strdup ( our_name_localized );
}

/* --------------------- */
char * memorainbow_async_get_description ( magic_api * api, int which, int mode )
{
	const char * our_desc_english;
	const char * our_desc_localized;

#ifdef DEBUG
	fprintf( stderr, "memorainbow_async_get_description\n");
#endif
	our_desc_english = tools[which].desc;
	our_desc_localized = gettext ( our_desc_english );
	return strdup ( our_desc_localized );
}

/* --------------------- */
int memorainbow_async_requires_colors ( magic_api * api, int which )
{
	return 0;	// FALSE
}

/* --------------------- */
int memorainbow_async_modes ( magic_api * api, int which )
{
  return(MODE_PAINT);
}


// Shut down
//
// explain how SDL_audioin could be patched to make Open/CloseAudioIn work
// symmetrically for the DEVDSP (Linux) driver

/* --------------------- */
void memorainbow_async_shutdown ( magic_api * api )
{
#ifdef DEBUG
	fprintf( stderr, "memorainbow_async_shutdown\n");
#endif
}


/* --------------------- */
static void memorainbow_async_linecb(void * ptr, int which,
		    SDL_Surface * canvas, SDL_Surface * last,
                    int x, int y)
{
  magic_api * api = (magic_api *) ptr;
	int f;
	int xx, yy, lastxx = -1, lastyy = -1;
	PaTime timetmp;
	if( (timetmp = ring_buffer_time[ring_head]) > max_buffer_time) {
	    int rt = ring_tail;
	    for( f=0; f < FRAMES_PER_BUFFER; f++) {
		unsigned int memorainbow_async_color = (unsigned int)ceilf( fabsf(ring_buffer[rt][f][0]  * (float)NUM_MEMORAINBOW_COLORS));
		if( (memorainbow_async_color >= NUM_MEMORAINBOW_COLORS) || (memorainbow_async_color <= 2))
			continue;	// don't draw clipping at all

		// plot the scope_buffer as a radial graph centred around the cursor
		xx = x + (int)((float)MEMORAINBOW_RADIUS * ring_buffer[rt][f][0]*cosftable[f]);
		yy = y + (int)((float)MEMORAINBOW_RADIUS * ring_buffer[rt][f][0]*sinftable[f]);
		if( (xx != lastxx) || (yy != lastyy)) {
			Uint32 memorainbow_async_rgb = SDL_MapRGB(canvas->format,
						memorainbow_async_hexes[memorainbow_async_color][0],
						memorainbow_async_hexes[memorainbow_async_color][1],
						memorainbow_async_hexes[memorainbow_async_color][2]);
			api->putpixel(canvas, xx, yy, memorainbow_async_rgb);
			lastxx = xx;
			lastyy = yy;
		}
		
	    }
	    max_buffer_time = timetmp;
	}
}

/* --------------------- */
void memorainbow_async_drag ( magic_api * api, int which, SDL_Surface * canvas,
                    SDL_Surface * snapshot, int ox, int oy, int x, int y,
                    SDL_Rect * update_rect )
{
  api->line((void *) api, which, canvas, snapshot, ox, oy, x, y, 1, memorainbow_async_linecb);

  if (ox > x) { int tmp = ox; ox = x; x = tmp; }
  if (oy > y) { int tmp = oy; oy = y; y = tmp; }

  update_rect->x = ox - MEMORAINBOW_RADIUS;
  update_rect->y = oy - MEMORAINBOW_RADIUS;
  update_rect->w = 2*MEMORAINBOW_RADIUS;
  update_rect->h = 2*MEMORAINBOW_RADIUS;
}


/* --------------------- */
void memorainbow_async_click ( magic_api * api, int which, int mode,
                     SDL_Surface * canvas, SDL_Surface * snapshot,
                     int x, int y, SDL_Rect * update_rect )
{
  memorainbow_async_drag(api, which, canvas, snapshot, x, y, x, y, update_rect);
}



/* --------------------- */
void memorainbow_async_release ( magic_api * api, int which,
                       SDL_Surface * canvas, SDL_Surface * snapshot,
                       int x, int y, SDL_Rect * update_rect )
{
#ifdef DEBUG
	fprintf( stderr, "memorainbow_async_release\n");

#endif
}


/* --------------------- */
void memorainbow_async_set_color ( magic_api * api, Uint8 r, Uint8 g, Uint8 b )
{
	// this tool uses no color
}

/* --------------------- */
void memorainbow_async_switchin ( magic_api * api, int which, int mode, SDL_Surface * canvas )
{
#ifdef DEBUG
	fprintf( stderr, "memorainbow_async_switchin\n");
#endif
	init_portaudio();
	return;
}

/* --------------------- */
void memorainbow_async_switchout ( magic_api * api, int which, int mode, SDL_Surface * canvas )
{
#ifdef DEBUG
	fprintf( stderr, "memorainbow_async_switchout\n");
#endif
	Pa_Terminate();
	return;
}

